﻿namespace etracks.Helpers
{
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Linq;
    using System.Security.Cryptography;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading.Tasks;

    public class CryptographyHelper
    {
        private static RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

        public class AES
        {
            public static string Encrypt(string plainText)
            {
                string cipherText = string.Empty;

                using (AesManaged myAes = new AesManaged())
                {

                }

                return cipherText;
            }

        } // public class AES

        

        public static string Hash(string plainText)
        {
            byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);

            SHA512 hasher = new SHA512Managed();

            return Convert.ToBase64String(hasher.ComputeHash(plainTextBytes));
        }

        #region comment out
        // Bad idea
        //public static string Hash2(string plainText)
        //{
        //    byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
        //    SHA512 hasher = new SHA512Managed();
        //    return BytesToString(hasher.ComputeHash(plainTextBytes));
        //}

        //public static string BytesToString(byte[] bytes)
        //{
        //    StringBuilder result = new StringBuilder(bytes.Length * 2);

        //    foreach (byte b in bytes)
        //        result.AppendFormat("{0:x}", b);

        //    return result.ToString();
        //}
        #endregion


        public static void Random()
        {
            using (RandomNumberGenerator rng = RNGCryptoServiceProvider.Create())
            {
                // Create a byte array to hold the random value. 
                byte[] randomNumber = new byte[1];
            }
        }

        public static string GetPrintablePassword(int targetLength)
        {
            //int targetLength = 8;
            char[] charSet = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();

            bool acceptPassword = false;
            bool hasDigit = false;
            bool hasLowerCaseLetter = false;
            bool hasUpperCaseLetter = false;

            int idx;
            char[] newPasswordChar;
            string newPassword = string.Empty;

            acceptPassword = false;
            hasDigit = false;
            hasLowerCaseLetter = false;
            hasUpperCaseLetter = false;
            newPasswordChar = new char[targetLength];

            while (!acceptPassword)
            {
                using (RandomNumberGenerator rng = RNGCryptoServiceProvider.Create())
                {
                    // Create a byte array to hold the random value. 
                    byte[] randomNumber = new byte[targetLength];

                    rng.GetBytes(randomNumber);

                    for (int i = 0; i < targetLength; i++)
                    {
                        idx = (int)Math.Truncate(((double)randomNumber[i] / byte.MaxValue) * 61);
                        newPasswordChar[i] = charSet[idx];
                    }

                    newPassword = new String(newPasswordChar);

                    // Check if new password is valid 
                    // new Regex("^[A-Za-z0-9]+$) <= Not completely correct!
                    // What we want to say is that newPassword should have 1 UCASE, 1 LCASE, and 1 DIGIT minimal

                    hasDigit = Regex.IsMatch(newPassword, "[0-9]+"); ;
                    hasLowerCaseLetter = Regex.IsMatch(newPassword, "[a-z]+"); ;
                    hasUpperCaseLetter = Regex.IsMatch(newPassword, "[A-Z]+");

                    if (hasDigit && hasLowerCaseLetter && hasUpperCaseLetter)
                    {
                        // accept 
                        acceptPassword = true;
                        break;
                    }
                }

            } // end while (!acceptPassword)

            return newPassword;
        }

        // Incomplete implementation of RSA encryption 
        // Reference: https://msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider(v=vs.110).aspx
        public static byte[] RSAEncrypt(byte[] DataToEncrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding)
        {
            try
            {
                byte[] encryptedData;
                //Create a new instance of RSACryptoServiceProvider. 
                using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
                {
                    
                    //Import the RSA Key information. This only needs 
                    //toinclude the public key information.
                    RSA.ImportParameters(RSAKeyInfo);

                    //Encrypt the passed byte array and specify OAEP padding.   
                    //OAEP padding is only available on Microsoft Windows XP or 
                    //later.  
                    encryptedData = RSA.Encrypt(DataToEncrypt, DoOAEPPadding);
                }
                return encryptedData;
            }
            //Catch and display a CryptographicException   
            //to the console. 
            catch (CryptographicException e)
            {
                Console.WriteLine(e.Message);

                return null;
            }

        }

        public static byte[] RSADecrypt(byte[] DataToDecrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding)
        {
            try
            {
                byte[] decryptedData;
                //Create a new instance of RSACryptoServiceProvider. 
                using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
                {
                    //Import the RSA Key information. This needs 
                    //to include the private key information.
                    RSA.ImportParameters(RSAKeyInfo);

                    //Decrypt the passed byte array and specify OAEP padding.   
                    //OAEP padding is only available on Microsoft Windows XP or 
                    //later.  
                    decryptedData = RSA.Decrypt(DataToDecrypt, DoOAEPPadding);
                }
                return decryptedData;
            }
            //Catch and display a CryptographicException   
            //to the console. 
            catch (CryptographicException e)
            {
                Console.WriteLine(e.ToString());

                return null;
            }

        }

        // The below 2 methods are copied from 
        // https://msdn.microsoft.com/en-us/library/system.security.cryptography.rngcryptoserviceprovider(v=vs.110).aspx

        public static byte RollDice(byte numberSides)
        {
            if (numberSides <= 0)
                throw new ArgumentOutOfRangeException("numberSides");

            // Create a byte array to hold the random value. 
            byte[] randomNumber = new byte[1];
            do
            {
                // Fill the array with a random value.
                rng.GetBytes(randomNumber);
            }
            while (!IsFairRoll(randomNumber[0], numberSides));
            // Return the random number mod the number 
            // of sides.  The possible values are zero- 
            // based, so we add one. 
            return (byte)((randomNumber[0] % numberSides) + 1);
        }

        private static bool IsFairRoll(byte roll, byte numSides)
        {
            // There are MaxValue / numSides full sets of numbers that can come up 
            // in a single byte.  For instance, if we have a 6 sided die, there are 
            // 42 full sets of 1-6 that come up.  The 43rd set is incomplete. 
            int fullSetsOfValues = Byte.MaxValue / numSides;

            // If the roll is within this range of fair values, then we let it continue. 
            // In the 6 sided die case, a roll between 0 and 251 is allowed.  (We use 
            // < rather than <= since the = portion allows through an extra 0 value). 
            // 252 through 255 would provide an extra 0, 1, 2, 3 so they are not fair 
            // to use. 
            return roll < numSides * fullSetsOfValues;
        }

    } // public class CryptographyHelper

} // namespace etracks.Helpers
